<?php
declare (strict_types = 1);

namespace app\admin\model\project;

use bbweb\traits\ModelTrait;
use bbweb\basic\BaseModel;
use think\facade\Db;

/**
 * @mixin \think\Model
 */
class ProjectTasks extends BaseModel
{
    /**
     * 数据表主键
     * @var string
     */
    protected $pk = 'id';

    /**
     * 模型名称
     * @var string
     */
    protected $name = 'project_tasks';

    use ModelTrait;


    // 设置字段信息
    protected $schema = [
        'id'			=> 'int',
        'zid'			=> 'int',
        'project_id'    => 'int',
        'owner_id'      => 'int',
        'uid'           => 'int',
        'title'		    => 'varchar',
        'description'   => 'varchar',
        'punish_desc'   => 'varchar',//处罚缘由
        'swimlane_id'   => 'int',
        'status'		=> 'tinyint',
        'column_id'		=> 'tinyint',
        'position'		=> 'tinyint',
        'deep'		    => 'tinyint',
        'time_estimated'=> 'double',
        'time_spent'    => 'double',
        'rate_total'    => 'double',//总分
        'rate_audit'    => 'double',//评测得分
        'rate_work'     => 'double',//工作得分
        'rate_punish'   => 'double',//处罚分数
        'rate'          => 'tinyint',//评测1-10分
        'is_delete'		=> 'tinyint',
        'create_at'     => 'int',
        'moved_at'      => 'int',//移动时间
        'start_at'      => 'int',//进入第三栏开始时间
        'end_at'        => 'int',//移出第三栏结束
        'rate_at'       => 'int',//评分生效时间
        'update_at'     => 'int',
    ];

    /**
     * 添加任务
     * @param $data
     * @return array
     */
    public static function add($data)
    {
        $zid = self::where('project_id',$data['project_id'])->max('zid');
        $data['zid'] = (int) ($zid + 1);
        $position = 1;
        self::beginTrans();
        try {
            $task = self::create($data);
            if($task) {
                $task = $task->toArray();

                $move = self::movePosition($task['project_id'], $task['id'], $task['column_id'], $position, $task['swimlane_id'], false);
                if(!$move) {
                    throw new \think\Exception('项目排序失败！');
                }
                $tool = Projects::toolTask($data['project_id']);
                if(!$tool) {
                    throw new \think\Exception('项目统计失败！');
                }
                $event = self::fireEvents($task, $task['column_id'], $task['position'], $task['swimlane_id'], 'create');
                if(!$event) {
                    throw new \think\Exception('任务写入事件失败！');
                }
            } else {
                throw new \think\Exception('新任务写入失败！');
            }
            self::commitTrans();
        } catch(\Exception $e) {
            self::rollbackTrans();
            return ['code'=>0, 'msg'=>$e->getMessage()];
        }

        return ['code'=>2, 'msg'=>'添加成功'];
    }

    /**
     * 修改任务
     * @param $data
     * @return array
     */
    public static function edit($data, $id)
    {
        //$position = 1;
        self::beginTrans();
        try {
            $bool = self::update($data, ['id'=>$id]);
            if($bool) {
                $task = self::find($id)->toArray();
                /*if(isset($data['moved_at'])) {//不允许修改时改变栏目所以暂时取消
                    $move = self::movePosition($task['project_id'], $task['id'], $task['column_id'], $position, $task['swimlane_id'], false);
                }*/
                $event = self::fireEvents($task, $task['column_id'], $task['position'], $task['swimlane_id'], 'update');
                if(!$event) {
                    throw new \think\Exception('任务写入事件失败！');
                }
            } else {
                throw new \think\Exception('新任务修改失败！');
            }
            self::commitTrans();
        } catch(\Exception $e) {
            self::rollbackTrans();
            return ['code'=>0, 'msg'=>$e->getMessage()];
        }

        return ['code'=>2, 'msg'=>'修改成功'];
    }

    /**
     * @param $id
     * @param int $project_id
     * @return bool
     */
    public static function remove($id, $project_id = 0)
    {
        self::beginTrans();
        if( !self::update(['is_delete'=>1], ['id'=>$id]) ) {
            self::rollbackTrans();
            return false;
        }
        if($project_id) {
            $tool = Projects::toolTask($project_id);
            if(!$tool) {
                self::rollbackTrans();
                return false;
            }
        }
        self::commitTrans();
        return true;
    }
    /**
     * 获取项目全部任务
     * @param $project_id
     * @return array
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public static function getTasks($project_id, $owner_id = 0)
    {
        $query = self::where('project_id', $project_id);
        if($owner_id) $query->where('owner_id', $owner_id);
        return $query->where('is_delete',0)->order('swimlane_id ASC,column_id ASC,position ASC')->select()->toArray();
    }

    public static function checkMove($uid, array $data)
    {
        if(!$uid || !$data['task_id']) {
            return ['code'=>0, 'msg'=>'参数错误'];
        }
        $task = self::get($data['task_id'])->toArray();
        if($task['owner_id'] != $uid ) {
            return ['code'=>0, 'msg'=>'无移动权限'];
        }
        if($data['dst_column_id']==4 && ($data['dst_column_id'] - $data['src_column_id'] > 1) ) {
            return ['code'=>0, 'msg'=>'栏目不能跳级进入已完成'];
        }
        if($data['src_column_id'] > $data['dst_column_id']) {
            return ['code'=>0, 'msg'=>'只能升级不能降级'];
        }
        if($task['status'] == 2 ) {
            return ['code'=>0, 'msg'=>'已完成不能再移动'];
        }
        return ['code'=>1, 'msg'=>'ok'];
    }
    /**
     * 移动项目
     * @param $project_id
     * @param $task_id
     * @param $column_id
     * @param $position
     * @param int $swimlane_id
     * @param bool $fire_events
     * @return bool
     */
    public static function movePosition($project_id, $task_id, $column_id, $position, $swimlane_id = 0, $fire_events = true, $audit = 0)
    {
        if ($position < 1) {
            return false;
        }
        $task = self::get($task_id)->toArray();
        $result = false;

        if ($task['swimlane_id'] != $swimlane_id) {
            $result = self::saveSwimlaneChange($project_id, $task_id, $position, $task['column_id'], $column_id, $task['swimlane_id'], $swimlane_id);
        } elseif ($task['column_id'] != $column_id) {
            $r1 = self::saveColumnChange($project_id, $task_id, $position, $swimlane_id, $task['column_id'], $column_id);
            $r2 = self::saveTaskTimestamps($task_id, $task['column_id'], $column_id, $task, $audit);
            $r3 = true;
            if($column_id == 4) {
                $r3 = self::toolTaskRate($task_id);
            }
            $tool = Projects::toolTask($project_id);

            $result = $r1 && $r2 && $r3 && $tool;
        } elseif ($task['position'] != $position) {
            $result = self::savePositionChange($project_id, $task_id, $position, $column_id, $swimlane_id);
        }

        if ($result && $fire_events) {
            self::fireEvents($task, $column_id, $position, $swimlane_id);
        }

        return $result;
    }

    /**
     * 变更泳道
     * @param $project_id
     * @param $task_id
     * @param $position
     * @param $original_column_id
     * @param $new_column_id
     * @param $original_swimlane_id
     * @param $new_swimlane_id
     * @return bool
     */
    private static function saveSwimlaneChange($project_id, $task_id, $position, $original_column_id, $new_column_id, $original_swimlane_id, $new_swimlane_id)
    {
        $r1 = self::saveTaskPositions($project_id, $task_id, 0, $original_column_id, $original_swimlane_id);
        $r2 = self::saveTaskPositions($project_id, $task_id, $position, $new_column_id, $new_swimlane_id);

        return $r1 && $r2;
    }

    /**
     * 变更栏目
     * @param $project_id
     * @param $task_id
     * @param $position
     * @param $swimlane_id
     * @param $original_column_id
     * @param $new_column_id
     * @return bool
     */
    private static function saveColumnChange($project_id, $task_id, $position, $swimlane_id, $original_column_id, $new_column_id)
    {
        $r1 = self::saveTaskPositions($project_id, $task_id, 0, $original_column_id, $swimlane_id);
        $r2 = self::saveTaskPositions($project_id, $task_id, $position, $new_column_id, $swimlane_id);

        return $r1 && $r2;
    }

    /**
     * 变更排序
     * @param $project_id
     * @param $task_id
     * @param $position
     * @param $column_id
     * @param $swimlane_id
     * @return bool
     */
    private static function savePositionChange($project_id, $task_id, $position, $column_id, $swimlane_id)
    {
        $result = self::saveTaskPositions($project_id, $task_id, $position, $column_id, $swimlane_id);

        return $result;
    }

    /**
     * @param $task_id
     * @param $original_column_id
     * @param $new_column_id
     * @param $task
     * @return ProjectTasks
     */
    private static function saveTaskTimestamps($task_id, $original_column_id, $new_column_id, $task, $audit = 0)
    {
        $now = time();
        $data = ['moved_at'=>$now];
        if($original_column_id==3 && $new_column_id==4) {
            //改变状态为已完成status=1，end_at赋值，计算工时
            $moved_at = $task['start_at'] ? $task['start_at'] : $task['moved_at'];
            $data['time_spent'] = Db::raw('time_spent+' . ProWorkdays::getWorkingHours($moved_at, time()));
            $data['status'] = $audit==0 ? 2 : 1;
            $data['end_at'] = $now;
            if($audit==0) $data['rate_at'] = $now;
        }
        if($original_column_id!=4 && $new_column_id==3) {
            $data['start_at'] = $now;
        }
        $res =  self::update($data,['id'=>$task_id]);
        return $res;
    }

    public static function toolTaskRate($task_id) {
        $rate = self::where('id', $task_id)->value('rate');
        $rate = $rate - 5;
        //更新工作得分 预计工时 + （预计工时 - 实际工时）
        $res =  self::where('id', $task_id)->update([
            'rate_work' => Db::raw('time_estimated + time_estimated - time_spent'),
            'rate_audit' => Db::raw('time_estimated * '. $rate .' * 0.1'),
            'update_at' => time()-1,
        ]);
        $res2 =  self::where('id', $task_id)->update([
            'rate_total' => Db::raw('rate_work + rate_audit - rate_punish'),
            'update_at' => time()-2,
        ]);
        return $res && $res2;
    }

    /**
     * 排序变更
     * @param $project_id
     * @param $task_id
     * @param $position
     * @param $column_id
     * @param $swimlane_id
     * @return bool
     */
    private static function saveTaskPositions($project_id, $task_id, $position, $column_id, $swimlane_id)
    {
        $tasks_ids = self::where('project_id', $project_id)
            ->where('swimlane_id', $swimlane_id)
            ->where('column_id', $column_id)
            ->where('id', '<>', $task_id)
            ->order('position ASC')
            ->order('id ASC')
            ->column('id','id');

        $offset = 1;

        foreach ($tasks_ids as $current_task_id) {

            //插入新的项目
            if ($position == $offset) {
                if (!self::saveTaskPosition($task_id, $offset, $column_id, $swimlane_id)) {
                    return false;
                }
                $offset++;
            }

            // 重写其他任务位置
            if (!self::saveTaskPosition($current_task_id, $offset, $column_id, $swimlane_id)) {
                return false;
            }

            $offset++;
        }

        // 在底部插入新任务并规范化错误位置
        if ($position >= $offset && !self::saveTaskPosition($task_id, $offset, $column_id, $swimlane_id)) {
            return false;
        }
        return true;
    }

    /**
     * 排序变更
     * @param $task_id
     * @param $position
     * @param $column_id
     * @param $swimlane_id
     * @return bool
     */
    private static function saveTaskPosition($task_id, $position, $column_id, $swimlane_id)
    {
        $result = self::where('id', $task_id)->update([
            'position'      => $position,
            'column_id'     => $column_id,
            'swimlane_id'   => $swimlane_id,
            'update_at'     => time()
        ]);

        if (!$result) {
            return false;
        }

        return true;
    }

    /**
     * 事件
     * @param array $task
     * @param $new_column_id
     * @param $new_position
     * @param $new_swimlane_id
     * @param string $event_name
     * @return ProjectTaskLogs|\think\Model
     */
    private static function fireEvents(array $task, $new_column_id, $new_position, $new_swimlane_id, $event_name = '')
    {
        $swimlanes = Swimlanes::getSwimlanes($task['project_id']);
        $swims = [];
        foreach ($swimlanes as $val) {
            $swims[$val['id']] = $val['name'];
        }
        $columns = ProjectColumns::getColumn($task['project_id']);
        $changes = [
            'id'                => $task['id'],
            'zid'               => $task['zid'],
            'title'             => $task['title'],
            'project_id'        => $task['project_id'],
            'swimlane_id'       => $task['swimlane_id'],//原泳道
            'swimlane_name'     => $swims[$task['swimlane_id']]??'--/--',//原泳道名称
            'position'          => $new_position,//新位置
            'column_id'         => $new_column_id,
            'new_swimlane_id'   => $new_swimlane_id,//新泳道
            'new_swimlane_name' => $swims[$new_swimlane_id]??'--/--',//新泳道名称
            'src_column_id'     => $task['column_id'],//原栏目
            'src_column_name'   => $columns[$task['column_id']] ? $columns[$task['column_id']]['title'] : '--/--',//原栏目名称
            'dst_column_id'     => $new_column_id,//新栏目
            'dst_column_name'   => $columns[$new_column_id] ? $columns[$new_column_id]['title'] : '--/--',//新栏目名称
            'date_moved'        => time(),
        ];

        if ($task['swimlane_id'] != $new_swimlane_id) {
            $event_name = 'move_swimlane';
        } elseif ($task['column_id'] != $new_column_id) {
            $event_name = 'move_column';
        } elseif ($task['position'] != $new_position) {
            $event_name = 'move_position';
        }
        return ProjectTaskLogs::addLogs($task['project_id'], $task['id'], $event_name, $changes);
    }
}
